#include "../log.h"
#include "../core.h"
#include "mips.h"
#include "int.h"
#include "tables.h"

#include "../hle.h"
#include "../host/host.h"

#define MAX_DBG 200
#define MAX_BACKLOG 100

char dbg[MAX_DBG];
u32 backlog[MAX_BACKLOG];
u32 op;

/** Various functions for the interpreter **/

/* Print some debug info, useful for things that crash because of the current instruction */
void cpu_printDebug()
{
#ifdef DEBUG
    s32 i;
    for (i = MAX_BACKLOG - 1; i >= 0; i--)
    {
        u32 addr = backlog[i];
        cpu_debug(dbg, MAX_DBG, mem_read32(addr));
        _log(INF, CPU, "From 0x%08x: %s", addr, dbg);
    }
    cpu_showReg();
#endif
}

void cpu_readPc()
{
    if (!mem_isOk(cpu.pc))
    {
        _log(ERR, CPU, "Wrong PC %08x! Exiting", cpu.pc);
        core_stop();
        return;
    }

    op = mem_read32(cpu.pc);
}

void cpu_printOp(u32 pc)
{
#ifdef DEBUG
    u32 i;
    for (i = MAX_BACKLOG - 1; i > 0; i--)
        backlog[i] = backlog[i - 1];
    backlog[0] = pc;
#endif
    /* Write the instruction info */
    if (debug_level == FUL)
        cpu_debug(dbg, MAX_DBG, op);
    _log(FUL, CPU, "%08x [%08x]: %s", pc, op, dbg);
}

/* Single step of the interpreter */
void cpu_singleStep()
{
    /* Read PC, and store it in OP */
    cpu_readPc();

    cpu.pc += 4;

    /* Prints the current instruction if enabled */
    cpu_printOp(cpu.pc - 4);

    /* Interpret the OP */
    cpu_interpret(op);
}

/* Single step, in the delay slot, then set next PC as 'addr' */
void cpu_delayBranchTo(u32 addr)
{
    cpu_readPc();

    cpu_printOp(cpu.pc);

    cpu.pc = addr;

    cpu_interpret(op);
}

/** High-level instructions **/
void int_cache(u32 op)
{
    _log(TRC, CPU, "Cache instruction at %08x: %d %d", MEMADDR, (op & 0x30000) >> 16, (op & 0x1c0000) >> 18);
}

void int_syscall(u32 op)
{
    hle_callSyscall(op);
}

/** Branch to **/
void int_beq(u32 op)
{
    if (R(_RT) == R(_RS))
        cpu_delayBranchTo(BRANCHADDR);
}

void int_bne(u32 op)
{
    if (R(_RT) != R(_RS))
        cpu_delayBranchTo(BRANCHADDR);
}

void int_blez(u32 op)
{
    if (R(_RS) <= 0)
        cpu_delayBranchTo(BRANCHADDR);
}

void int_bgtz(u32 op)
{
    if (R(_RS) > 0)
        cpu_delayBranchTo(BRANCHADDR);
}

void int_bltz(u32 op)
{
    if (R(_RS) < 0)
        cpu_delayBranchTo(BRANCHADDR);
}

void int_bgez(u32 op)
{
    if (R(_RS) >= 0)
        cpu_delayBranchTo(BRANCHADDR);
}

void int_beql(u32 op)
{
    if (R(_RT) == R(_RS))
        cpu_delayBranchTo(BRANCHADDR);
    else
        PC += 4;
}

void int_bnel(u32 op)
{
    if (R(_RT) != R(_RS))
        cpu_delayBranchTo(BRANCHADDR);
    else
        PC += 4;
}

void int_blezl(u32 op)
{
    if (R(_RS) <= 0)
        cpu_delayBranchTo(BRANCHADDR);
    else
        PC += 4;
}

void int_bgtzl(u32 op)
{
    if (R(_RS) > 0)
        cpu_delayBranchTo(BRANCHADDR);
    else
        PC += 4;
}

void int_bltzl(u32 op)
{
    if (R(_RS) < 0)
        cpu_delayBranchTo(BRANCHADDR);
    else
        PC += 4;
}

void int_bgezl(u32 op)
{
    if (R(_RS) >= 0)
        cpu_delayBranchTo(BRANCHADDR);
    else
        PC += 4;
}

/** Jump functions **/
void int_j(u32 op)
{
    cpu_delayBranchTo((PC & 0xf0000000) | ((op & 0x3ffffff) << 2));
}

void int_jal(u32 op)
{
    R(RA) = PC + 4;
    cpu_delayBranchTo((PC & 0xf0000000) | ((op & 0x3ffffff) << 2));
}

void int_jr(u32 op)
{
    cpu_delayBranchTo(R(_RS));
}

void int_jalr(u32 op)
{
    if (_RD != ZR)
        R(_RD) = PC + 4;
    cpu_delayBranchTo(R(_RS));
}

/** Immediate functions **/
void int_addi(u32 op)
{
    if (_RT != ZR)
        R(_RT) = R(_RS) + SOFFSET;
}

void int_addiu(u32 op)
{
    if (_RT != ZR)
        R(_RT) = R(_RS) + SOFFSET;
}

void int_slti(u32 op)
{
    if (_RT != ZR)
        R(_RT) = R(_RS) < (s32)SOFFSET;
}

void int_sltiu(u32 op)
{
    if (_RT != ZR)
        R(_RT) = (u32)R(_RS) < (u32)(s32)SOFFSET;
}

void int_andi(u32 op)
{
    if (_RT != ZR)
        R(_RT) = R(_RS) & UOFFSET;
}

void int_ori(u32 op)
{
    if (_RT != ZR)
        R(_RT) = R(_RS) | UOFFSET;
}

void int_xori(u32 op)
{
    if (_RT != ZR)
        R(_RT) = R(_RS) ^ UOFFSET;
}

void int_lui(u32 op)
{
    if (_RT != ZR)
        R(_RT) = UOFFSET << 16;
}

/** Various **/

void int_movz(u32 op)
{
    if (R(_RT) == 0)
        R(_RD) = R(_RS);
}

void int_movn(u32 op)
{
    if (R(_RT) != 0)
        R(_RD) = R(_RS);
}

/** Arithmetic **/

void int_add(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) + R(_RT);
}

void int_addu(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) + R(_RT);
}

void int_sub(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) - R(_RT);
}

void int_subu(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) - R(_RT);
}

void int_and(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) & R(_RT);
}

void int_or(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) | R(_RT);
}

void int_xor(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) ^ R(_RT);
}

void int_nor(u32 op)
{
    if (_RD != ZR)
        R(_RD) = ~(R(_RS) | R(_RT));
}

void int_slt(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RS) < R(_RT);
}

void int_sltu(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (u32)R(_RS) < (u32)R(_RT);
}

void int_max(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (R(_RS) > R(_RT) ? R(_RS) : R(_RT));
}

void int_min(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (R(_RS) < R(_RT) ? R(_RS) : R(_RT));
}

/** Memory read & write **/

void int_lb(u32 op)
{
    if (_RT != ZR)
        R(_RT) = mem_read8(MEMADDR);
}

void int_lh(u32 op)
{
    if (_RT != ZR)
        R(_RT) = mem_read16(MEMADDR);
}

void int_lw(u32 op)
{
    if (_RT != ZR)
        R(_RT) = mem_read32(MEMADDR);
}

void int_lbu(u32 op)
{
    if (_RT != ZR)
        R(_RT) = (u32)(u8)mem_read8(MEMADDR);
}

void int_lhu(u32 op)
{
    if (_RT != ZR)
        R(_RT) = (u32)(u16)mem_read16(MEMADDR);
}

void int_sb(u32 op)
{
    mem_write8(MEMADDR, (u8)(R(_RT) & 0xff));
}

void int_sh(u32 op)
{
    mem_write16(MEMADDR, (u16)(R(_RT) & 0xffff));
}

void int_sw(u32 op)
{
    mem_write32(MEMADDR, (u32)R(_RT));
}

void int_lwl(u32 op)
{
    u8 shift = (MEMADDR & 3) * 8;
    u32 mem = mem_read32(MEMADDR & 0xfffffffc);
    if (_RT != ZR)
        R(_RT) = ((u32)R(_RT) & (0x00ffffff >> shift))
                | (mem << (24 - shift));
}

void int_lwr(u32 op)
{
    u8 shift = (MEMADDR & 3) * 8;
    u32 mem = mem_read32(MEMADDR & 0xfffffffc);
    if (_RT != ZR)
        R(_RT) = ((u32)R(_RT) & (0xffffff00 << (24 - shift))) 
                | (mem >> shift);
}

void int_swl(u32 op)
{
    u8 shift = (MEMADDR & 3) * 8;
    u32 addr = MEMADDR & 0xfffffffc;
    u32 mem = mem_read32(addr);
    mem_write32(addr, (((u32)R(_RT) >> (24 - shift)))
                     | (mem & (0xffffff00 << shift)));
}

void int_swr(u32 op)
{
    u8 shift = (MEMADDR & 3) * 8;
    u32 addr = MEMADDR & 0xfffffffc;
    u32 mem = mem_read32(addr);
    mem_write32(addr, (((u32)(R(_RT)) << shift)
                     | (mem & (0x00ffffff >> (24 - shift)))));
}

/** Various **/

void int_clz(u32 op)
{
    u32 v = R(_RS);
    if (_RD != ZR)
        for (R(_RD) = 0; R(_RD) <= 31; R(_RD)++)
            if (v & (1 << (31 - R(_RD))))
                break;
}

void int_clo(u32 op)
{
    u32 v = R(_RS);
    if (_RD != ZR)
        for (R(_RD) = 0; R(_RD) <= 31; R(_RD)++)
            if (!(v & (1 << (31 - R(_RD)))))
                break;
}

/** Multiplication & Division; HI/LO instructions **/

typedef struct {
    s32 lo;
    s32 hi;
} int64;

typedef union {
    int64 dec;
    s64 var;
} longvar;

typedef union {
    int64 dec;
    u64 var;
} ulongvar;

void int_mult(u32 op)
{
    longvar result;

    result.var = ((s64)R(_RS)) * ((s64)R(_RT));
    LO = result.dec.lo;
    HI = result.dec.hi;
}

void int_multu(u32 op)
{
    ulongvar result;

    result.var = ((u64)(u32)R(_RS)) * ((u64)(u32)R(_RT));
    LO = result.dec.lo;
    HI = result.dec.hi;
}

void int_madd(u32 op)
{
    longvar orig, result;
    orig.dec.hi = HI;
    orig.dec.lo = LO;
    result.var = orig.var + (s64)R(_RS) * (s64)R(_RT);
    HI = result.dec.hi;
    LO = result.dec.lo;
}

void int_maddu(u32 op)
{
    ulongvar orig, result;
    orig.dec.hi = HI;
    orig.dec.lo = LO;
    result.var = orig.var + (u64)R(_RS) * (u64)R(_RT);
    HI = result.dec.hi;
    LO = result.dec.lo;
}

void int_msub(u32 op)
{
    longvar orig, result;
    orig.dec.hi = HI;
    orig.dec.lo = LO;
    result.var = orig.var - (((s64)R(_RS) * (s64)R(_RT)));
    HI = result.dec.hi;
    LO = result.dec.lo;
}

void int_msubu(u32 op)
{
    ulongvar orig, result;
    orig.dec.hi = HI;
    orig.dec.lo = LO;
    result.var = orig.var - (((u64)R(_RS) * (u64)R(_RT)));
    HI = result.dec.hi;
    LO = result.dec.lo;
}

void int_mfhi(u32 op)
{
    if (_RD != ZR)
        R(_RD) = HI;
}

void int_mthi(u32 op)
{
    HI = R(_RS);
}

void int_mflo(u32 op)
{
    if (_RD != ZR)
        R(_RD) = LO;
}

void int_mtlo(u32 op)
{
    LO = R(_RS);
}

void int_div(u32 op)
{
    s32 a = R(_RS);
    s32 b = R(_RT);
    if (b == 0) {
        _log(ERR, CPU, "Dividing by 0 !");
        return;
    }
    LO = (s32)(a / b);
    HI = (s32)(a % b);
}

void int_divu(u32 op)
{
    u32 a = R(_RS);
    u32 b = R(_RT);
    if (b == 0) {
        _log(ERR, CPU, "Dividing by 0 !");
        return;
    }
    LO = (a / b);
    HI = (a % b);
}

void int_sll(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (u32)R(_RT) << _SA;
}

void int_srl(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (u32)R(_RT) >> _SA;
}

void int_sra(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RT) >> _SA;
}

void int_sllv(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (u32)R(_RT) << (R(_RS) & 0x1f);
}

void int_srlv(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (u32)R(_RT) >> (R(_RS) & 0x1f);
}

void int_srav(u32 op)
{
    if (_RD != ZR)
        R(_RD) = R(_RT) >> (R(_RS) & 0x1f);
}

void int_rotr(u32 op)
{
    s32 shift = _SA;
    if (_RD != ZR)
        R(_RD) = (R(_RT) >> shift) | (R(_RT) << (32 - shift));
}

void int_rotrv(u32 op)
{
    s32 shift = R(_RS) & 0x1f;
    if (_RD != ZR)
        R(_RD) = (R(_RT) >> shift) | (R(_RT) << (32 - shift));
}

void int_seb(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (s32)(s8)R(_RT);
}

void int_seh(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (s32)(s16)R(_RT);
}

void int_bitrev(u32 op)
{
    if (_RD != ZR)
    {
        u8 i;
        R(_RD) = 0;
        for (i = 0; i < 32; i++)
            if (R(_RT) & (1 << i))
                R(_RD) |= (0x80000000 >> i);
    }
}

void int_wsbw(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (((R(_RT) & 0xff000000) >> 24) | ((R(_RT) & 0x00ff0000) >>  8)
               |  ((R(_RT) & 0x0000ff00) <<  8) | ((R(_RT) & 0x000000ff) << 24));

}

void int_wsbh(u32 op)
{
    if (_RD != ZR)
        R(_RD) = (((R(_RT) & 0xff000000) >> 8) | ((R(_RT) & 0x00ff0000) << 8)
               |  ((R(_RT) & 0x0000ff00) >> 8) | ((R(_RT) & 0x000000ff) << 8));

}

void int_ext(u32 op)
{
    s32 pos = _POS;
    s32 size = _END + 1;
    s32 mask = ~(~0 << size) << pos;

    if (_RT != ZR)
        R(_RT) = (R(_RS) & mask) >> pos;
}

void int_ins(u32 op)
{
    u32 pos = _POS; /* _POS: lsb ; _END: msb */
    u32 size = _END - pos + 1;
    u32 mask = ~(~0 << size) << pos;

    if (_RT != ZR)
        R(_RT) = (R(_RT) & ~mask) | ((R(_RS) << pos) & mask);
}

void int_interrupt(u32 op)
{
    if((op & 1) == 0)
        _log(INF, CPU, "Disable/Enable interrupt");
}

void int_pref(u32 op)
{
    _log(WRN, CPU, "pref with mode %d", _RT);
}

void int_break(u32 op)
{
    _log(WRN, CPU, "[%08x] break received - exiting", op);
    core_stop();
}

void int_mfic(u32 op)
{
    _log(ERR, CPU, "mfic not implemented: %08x", R(_RT));
}

void int_mtic(u32 op)
{
    _log(ERR, CPU, "mtic not implemented: %08x", R(_RT));
}

